#pragma once
#include <algorithm>
#include <unistd.h>
#include <utility>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "Httpserver.hpp"
#include "Util.hpp"

#define SEP ": "
#define LINE_END "\r\n"
#define HTTP_VERSION "HTTP/1.0"
#define OK 200
#define NOT_FOUND 404
#define SERVER_ERROR 500
#define PAGE_404 "404.html"
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
using std::cout;
using std::endl;

class HttpRequest
{
    public:
    std::string method_;
    std::string uri_; //path?args
    std::string version_;

    std::string request_line_;
    std::vector<std::string> request_header_;
    std::string request_blank_;
    std::string request_body_;
    std::unordered_map<std::string,std::string> request_kv_;
    int content_length_;
    std::string path_;
    std::string query_string_;
    bool cgi_;
    public:
    HttpRequest()
    :content_length_(0),cgi_(false)
    {}
};

class HttpResponse
{
    public:
    std::vector<std::string> response_header_;
    std::string reponse_blank_;
    std::string reponse_body_;

    std::string status_line_;
    int fd_;
    int code_;
    std::string suffix_;
    int size_;
    HttpResponse()
    :code_(OK),fd_(-1),reponse_blank_(LINE_END)
    {}
   
    ~HttpResponse()
    {}
};

static std::string Suffix_To_Desc(std::string suffix)
{
    static std::unordered_map<std::string,std::string> Suffix_To_Desc_kv={
        {".html","text/html"},
        {".css","test/css"},
        {".js","application/javascript"},
        {".jpg","application/x-jpg"},
        {".xml","application/xml"},
    };
    auto e=Suffix_To_Desc_kv.find(suffix);
    if(e != Suffix_To_Desc_kv.end())
    {
        return e->second;
    }
    return "text/html";
}

class Endpoint
{
    public:
    Endpoint(int sock)
    :sock_(sock),stop_(false)
    {}

   
    bool Recv_Request_parse()
    {
        bool Recv=false,parse=false;
       if(Recv=Recv_Http_Request())
       {
           parse=parse_Http_Request();
       }
    //    cout<<"Recv && parse:"<<(Recv && parse) <<endl;
       return Recv && parse;
    }
    bool Build_Response_Send()
    {
        bool Build=false,Send=false;
        if(Build=Build_Http_Response())
        {
            Send=Send_Http_Response();
        }
    //    cout<<"Build "<<(Build) <<endl;
    //    cout<<"Send "<<(Send ) <<endl;
        return Send && Build; 
        // && Send;
    }
 
    private:
    bool Recv_Http_Request()
    {
         bool Line =!Recv_Request_line();
         if(!Line) stop_=true;
         bool Header=false;
         if(!stop_)
         {
            Header =!Recv_Request_Header();
         }
         return Line && Header;
    } 
    bool parse_Http_Request()
    {
        bool Line =!parse_Request_Line();
        bool Header =!parse_Request_Header();
        bool Body =!Recv_Request_Body();
        Parse_Request_Body();
        return Body && Line && Header;
    }

    bool Build_Http_Response()
    {
        Build_Response_Header();
    }

    bool Send_Http_Response()
    {
        send(sock_,Rp_.status_line_.c_str(),Rp_.status_line_.size(),0);
        for(auto e:Rp_.response_header_)
        {
            send(sock_,e.c_str(),e.size(),0);
        }
        send(sock_,Rp_.reponse_blank_.c_str(),Rp_.reponse_blank_.size(),0);
        sendfile(sock_,Rp_.fd_,nullptr,Rp_.size_);
        send(sock_,Rp_.reponse_body_.c_str(),Rp_.reponse_body_.size(),0);
        // close(sock_);
    }


    bool Recv_Request_line()
    {
//             template <class InputIterator, class OutputIterator, class UnaryOperator>
//             OutputIterator transform (InputIterator first1, InputIterator last1,OutputIterator result, UnaryOperator op)
//              {
//                      while (first1 != last1)
//                      {
//                          *result = op(*first1);  // or: *result=binary_op(*first1,*first2++);
//                          ++result; ++first1;
//                      }
//                      return result;
//               }
        
        //对报文的请求行处理
        auto& request_line=Rq_.request_line_;
        if(Util::Readline(sock_,request_line) > 0)
        {
        // cout<<"-----------------"<<request_line<<endl;
            request_line.resize(request_line.size()-1);
            std::transform(Rq_.method_.begin(),Rq_.method_.end(),Rq_.method_.begin(),::toupper);
            LOG(INFO,request_line);
        }
        else
        {
            stop_=true;
            LOG(WARENING,request_line);
        }
        return stop_;
   
    }

    bool Recv_Request_Header()
    {
        std::string line;
        while(true)
        {
            line.clear();
            if(Util::Readline(sock_,line) <=0)
            {
                stop_=true;
                break;
            }
            if(line == "\n")
            {
                Rq_.request_blank_=line;
                break;
            }
            line.resize(line.size()-1);
            Rq_.request_header_.push_back(line);
            // LOG(INFO,line);
        }
        LOG(INFO,"stop debug:"+stop_);
        return stop_;
    }
    bool parse_Request_Line()
    {
        auto& line=Rq_.request_line_;
        
        if(line.size()> 0)
        {
            //line.resize(line.size()-1);//去除‘\n’
            std::stringstream ss(line);
            ss>>Rq_.method_>>Rq_.uri_>>Rq_.version_;
            // cout<<Rq_.method_<<Rq_.uri_<<Rq_.version_<<endl;
            // LOG(INFO,line);
        }
        else
        {
            stop_=true;
            LOG(WARENING,line);
        }
        return stop_;
    }
    bool parse_Request_Header()
    {
        //分析报头，填入到 request_kv_
        std::string key,value;
        for(auto& e:Rq_.request_header_)
        {
            if(Util::Cutstring(e,key,value,SEP))
            {
                Rq_.request_kv_.insert(make_pair(key,value));
            }
            else
            {
                stop_=true;
                break;
            }
        }
        return stop_;
    }
    bool Is_Need_Recv_Request_Body()
    {
        auto& method=Rq_.method_;
        if(method == "POST")
        {
            auto & header_kv=Rq_.request_kv_;
            auto e=header_kv.find("Content-Length");
            if(e != header_kv.end())
            {
                //find返回一个迭代器，要使用->
                LOG(INFO,"POST,Content-Length:"+e->second);
                Rq_.content_length_=atoi(e->second.c_str());
                return true;
            }
            //Debug
            // cout<<"Rq_.content_length_:"<<Rq_.content_length_<<endl;
        }
        return false;
    }
    bool Recv_Request_Body()
    {
        if(Is_Need_Recv_Request_Body())
        {
            int content_length=Rq_.content_length_;
            auto& body=Rq_.request_body_;
            char ch=' ';
            while(content_length)
            {
                ssize_t s=recv(sock_,&ch,1,0);
                if(s>0)
                {
                    body.push_back(ch);
                    content_length--;
                }
                else
                {
                    stop_=true;
                    break;
                }
            }
            LOG(INFO,body);
        }
        return stop_;
    }

    int Process_Cgi()
    {
        LOG(INFO,"process cgi method!");
        int code=OK;
        auto& method=Rq_.method_;
        auto& query_string=Rq_.query_string_; //GET
        auto& body_text=Rq_.request_body_; //POST
        auto& bin=Rq_.path_;//存放可执行程序的地方
        auto& reponse_body=Rp_.reponse_body_;
        auto content_length=Rq_.content_length_;
        
        //下面是要将下面的变量导入到子进程当中
        std::string query_string_env;
        std::string method_env;
        std::string content_length_env;
        //站在父进程角度命名
        int input[2],output[2];
        //开始让父子进程开始通信
        if(pipe(input) < 0)
        {
            LOG(ERROR,"pipe create failed");
            code=NOT_FOUND;
            return code;
        }
        if(pipe(output) < 0)
        {
            LOG(ERROR,"pipe create failed");
            code=NOT_FOUND;
            return code;
        }
        //下面我们开始创建子进程调用我们的可执行程序。
        pid_t pid=fork();
        if(pid == 0)
        {
            //这里要注意进程的替换是将数据与代码同时替换，所以我们想要进程间通信我们要使用两个管道，或者使用环境变量
            //所以这里我们应该将四个文件描述符fd分配给父进程与子进程，所以我们要关掉某些fd
            close(input[0]);
            close(output[1]);
            //现在将环境变量控制好.
            method_env="METHOD=";
            method_env+=method;
            putenv((char*)method_env.c_str());
            if(method == "GET")
            {
                query_string_env="QUERY_STRING=";
                query_string_env+=query_string;
                putenv((char*)query_string_env.c_str());
                LOG(INFO,"GET METHOD_ENV input");
            }
            else if(method == "POST")
            {
                content_length_env="CONTENT_LENGTH=";
                content_length_env+=std::to_string(content_length);
                putenv((char*)content_length_env.c_str());
                LOG(INFO,"POST METHOD_ENV input");     
            }
            else
            {
                //No doing.
            }
            //但是这里我们子进程读取资源的时候，还存在一个问题因为在进程替换后，虽然管道没有关闭，但是我们的子进程就是不知道我们的管道文件描述符是几？
            //所以我们做个约定，我们令子进程从标准输入读父进程的数据，到标准输出当中写数据。
            // LOG(INFO,bin);
            dup2(input[1],1);
            dup2(output[0],0);
            execl(bin.c_str(),bin.c_str(),nullptr);
            exit(1);
        }
        else if(pid >0)
        {
            close(input[1]);
            close(output[0]);
            //我们要将正文数据写入到管道中，然后再让子进程读取。
            if(method == "POST")
            {
                const char* start=body_text.c_str();
                ssize_t total=0,size=0;
                //ssize_t write(int fd, const void *buf, size_t count);
                // cout<<start<<endl;
                // LOG(INFO,"will write to pipe");
                while(total < content_length && (size = write(output[1],start+total,body_text.size()-total)) > 0)
                {
                    total+=size;
                }
                // cout<<total<<endl;
            }

            //然后在我们将变量放到管道后，我们也要将子进程向管道管道里面写的内容读取出来，放到外我们的响应当中，
            char ch=0;
            while(read(input[0],&ch,1)>0)
            {
                reponse_body.push_back(ch);
            }
            int status=0;
            pid_t wait_id=waitpid(pid,&status,0);
            if(wait_id == pid)
            {
                if(WIFEXITED(status))
                {
                    if(WEXITSTATUS(status) ==0)
                    {
                        code = OK;
                    }
                    else
                    {
                        code =NOT_FOUND;
                    }
                }
                else
                {
                    code = NOT_FOUND;
                }
            }
            close(input[0]);
            close(output[1]);
            return code;
        }
        else
        {
            LOG(ERROR,"FORK ERROR");
            code =NOT_FOUND;
            return code;
        }
    }




    bool Parse_Request_Body()
    {
        std::string path; //解读出用户访问的资源路径
        auto& code=Rp_.code_;
        if(Rq_.method_ != "GET" && Rq_.method_ != "POST")
        {
            LOG(WARENING,"method isn't right");
            code=NOT_FOUND;
            end();
        }
        if(Rq_.method_ == "GET")
        {
            //开始分析带参的信息
            size_t pos=Rq_.uri_.find('?');
            if(pos != std::string::npos)
            {
                Util::Cutstring(Rq_.uri_,Rq_.path_,Rq_.query_string_,std::string("?"));
            }
            else
            {
                Rq_.path_=Rq_.uri_;
                //说明用户是要访问某个网页。
            }
        }
        else if(Rq_.method_ == "POST")
        {
            Rq_.cgi_=true;
            Rq_.path_=Rq_.uri_;
        }
        else
        {

        }
        path=Rq_.path_;
        Rq_.path_=WEB_ROOT;
        Rq_.path_+=path;
        if(Rq_.path_[Rq_.path_.size()-1] == '/')
        {
            //只说明用户要访问某个主页，但是不清楚访问的资源。
            Rq_.path_+=HOME_PAGE;
        }
        //这个结构体是专门查看文件资源的。
        // struct stat {
        //     dev_t     st_dev;         /* ID of device containing file */
        //     ino_t     st_ino;         /* Inode number */
        //     mode_t    st_mode;        /* File type and mode */
        //     nlink_t   st_nlink;       /* Number of hard links */
        //     uid_t     st_uid;         /* User ID of owner */
        //     gid_t     st_gid;         /* Group ID of owner */
        //     dev_t     st_rdev;        /* Device ID (if special file) */
        //     off_t     st_size;        /* Total size, in bytes */
        //     blksize_t st_blksize;     /* Block size for filesystem I/O */
        //     blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */
        // On success, zero is returned.  
        // On error, -1 is returned, and errno is set appropriately.
        struct stat st;
        int sz=0;
        if(stat(Rq_.path_.c_str(),&st) == 0)
        {
            //说明路径正确，有资源存在
            if(S_ISDIR(st.st_mode))
            {
                //说明用户请求的是一个目录，
                // 但是我们不应该给用户访问一整个目录的权限，我们应该返回该目录的主页给用户
                //同时该路径已经表明了路径不是以"/"结尾
                Rq_.path_+="/";
                Rq_.path_+=HOME_PAGE;
                stat(Rq_.path_.c_str(),&st);
            }
            //现在到了这一步已经说明了，
            // 此时用户要访问的是一个资源，但是这个资源有没有可能是一个可执行程序呢？
            if((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
            {
                Rq_.cgi_=true;
            }
            sz=st.st_size;
        }
        else
        {
            //表明系统当中没有对应的资源
            //像我们浏览网页经常发现404页面一样
            std::string info =Rq_.path_;
            info+=" ";
            info+="NOT FOUND";
            LOG(WARENING,info);
            code = NOT_FOUND;
            end();
        }
        Rp_.size_=sz;
        if(Rq_.cgi_)
        {
            Rp_.code_=Process_Cgi();
        }
        else
        {
            Rp_.code_=Process_Non_Cgi();
        }
        return true;
    }

    void end()
    {
        //专门用来记录一下建立的时候
        ;
    }

    int  Process_Non_Cgi()
    {
        Rp_.fd_=open(Rq_.path_.c_str(),O_RDONLY);
        if(Rp_.fd_ >=0)
        {
            LOG(INFO,Rq_.path_+"open sucess");
            return OK;
        }
        return NOT_FOUND;
    }

    bool BUild_200_Response()
    {
        std::string line="Content-Type：";
        line+=Suffix_To_Desc(Rp_.suffix_);
        line+=LINE_END;
        Rp_.response_header_.push_back(line);
        line="Content-Length：";
        if(Rq_.cgi_)
        {
            line+=std::to_string(Rp_.reponse_body_.size());
        }
        else
        {
            //Debug
            // cout<<line<<endl;
            line+=std::to_string(Rq_.content_length_);
        }
        line+=LINE_END;
        Rp_.response_header_.push_back(line);
        return true;
    }


    bool BUild_404_Response(string& page)
    {
        Rq_.cgi_=false;
        Rp_.fd_=open(page.c_str(),O_RDONLY);
        if(Rp_.fd_ > 0)
        {
            LOG(INFO,"open 404_page successed");
            struct stat st;
            stat(page.c_str(),&st);
            Rp_.size_=st.st_size;
            std::string line="Content-Type: text/html";
            line+=LINE_END;
            Rp_.response_header_.push_back(line);
            line="Content-Length: ";
            line+=std::to_string(st.st_size);
            line+=LINE_END;
            Rp_.response_header_.push_back(line);
        }
        else
        {
            LOG(WARENING,"open 404_page failed");
        }
        return true;
    }
    void BUild_500_Response()
    {
        ;
    }
    bool   Build_Response_Header()
    {
        //建立响应的响应行
        auto& code=Rp_.code_;
        auto& status_line=Rp_.status_line_;
        status_line+=HTTP_VERSION;
        status_line+=" ";
        //Debug
        // cout<<code<<":"<<status_line<<endl;

        status_line+=std::to_string(code);
        // status_line+=code;
        //Debug
        // cout<<code<<":"<<status_line<<endl;

        status_line+=" ";
        status_line+=Util::Code_To_Desc(code);
        status_line+=LINE_END;
        if(code == OK)
        {
            
            //Debug
            // cout<<code<<endl;
            return BUild_200_Response();
        }
        else if(code == NOT_FOUND)
        {
            string page=WEB_ROOT;
            page+='/';
            page+=PAGE_404;
            return BUild_404_Response(page);
        }
        else if(code == SERVER_ERROR)
        {
            BUild_500_Response();
        }
        else
        {
            return false;
        }

    }
    
  
   

    private:
    int sock_;
    HttpRequest Rq_;
    HttpResponse Rp_;
    bool stop_;
    
};
class CallBack
{
    public:
            CallBack()
            {}
            void operator()(int sock)
            {
               HandlerRequese(sock);
            }

        static void*HandlerRequese(int sock)
        {
            
#ifdef DEBUG
            cout<<"------------begin---------"<<endl;
            std::string message;
            while(Util::Readline(sock,message))
            {
                cout<<message;
                message.clear();
            }
            cout<<"------------end---------"<<endl;
#else
            Endpoint test(sock);
            // cout<<"sdadsadwadsa"<<endl;
            test.Recv_Request_parse();
            test.Build_Response_Send();
#endif
            // fflush();
            close(sock);
            return nullptr;
        }
        ~CallBack()
        {}
};
